package com.example.mq.mqserver.core;

import com.example.mq.common.MQException;

public class Router {
    /**
     * 判定 bindingKey 格式的合法性
     * 1. 由数字、字母、下划线组成
     * 2. 使用 . 将整体分割为若干个独立部分
     * 3. 允许存在两个通配符：* #
     * 4. 通配符必须是独立部分
     * @param bindingKey    bindingKey
     * @return              合法：true，非法：false
     */
    public boolean checkBindingKey(String bindingKey) {
        if (bindingKey.length() == 0) {
            // 允许为空字符串，在使用 DIRECT 和 FANOUT 交换机时，bindingKey 是没有用的
            return true;
        }
        // 1. 检查字符串中有没有非法字符
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            // 判定大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判定小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判定数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 判定 _ 或者 . 或者 * 或者 #
            if (ch == '_' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            // 非法字符
            return false;
        }
        // 2. 检查通配符是否是独立部分
        String[] words = bindingKey.split("\\.");
        // 直接写 . 是不行的，因为 . 在正则表达式中，是一个特殊的符号，所以在正则表达式中使用 \. 进行转义
        // 又因为在 Java 字符串中，\ 是一个特殊字符，所以需要再加一个 \ 对 \ 进行转义，写作 \\.
        // 也就是说
        // Java 中 "\\." 是 \.
        // 正则表达式中 \. 是 .

        for (String word : words) {
            // 如果 words 的长度大于 1，并且包含了通配符，就是非法格式
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }

        // 3. 约定一下，通配符之间的相邻关系（因为前3个实现匹配时比较繁琐，而且没啥用）
        // aaa.#.#.bbb 	=>	非法
        // aaa.#.*.bbb 	=>	非法
        // aaa.*.#.bbb 	=>	非法
        // aaa.*.*.bbb 	=>	合法
        for (int i = 0; i < words.length - 1; i++) {
            // 判定 #.#
            if (words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }
            // 判定 #.*
            if (words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }
            // 判定 *.#
            if (words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判定 routingKey 格式的合法性
     * 1. 由数字、字母、下划线组成
     * 2. 使用 . 将整体分割为若干个独立部分
     * @param routingKey    routingKey
     * @return              合法：true，非法：false
     */
    public boolean checkRoutingKey(String routingKey) {
        if (routingKey.length() == 0) {
            // 空字符串，合法的情况，比如在使用 FANOUT 交换机时，routingKey 是用不上的的，可以设为空
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            // 判定大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判定小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判定数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 判定下划线 或者 .
            if (ch == '_' || ch == '.') {
                continue;
            }
            // 非法字符
            return false;
        }
        return true;
    }

    /**
     * 根据不同的交换机实现不同的匹配规则
     * @param exchangeType  交换机类型
     * @param binding       绑定
     * @param message       消息
     * @return              匹配成功：true，匹配失败：false
     */
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MQException {
        if (exchangeType == ExchangeType.FANOUT) {
            // 如果是 FANOUT 交换机, 则该交换机上绑定的所有队列都需要转发
            return true;
        } else if (exchangeType == ExchangeType.TOPIC) {
            // 如果是 TOPIC 交换机, 规则就要更复杂一些.
            return routeTopic(binding, message);
        } else {
            throw new MQException("[Router] 交换机类型非法! exchangeType=" + exchangeType);
        }
    }

    // 实现 TOPIC 交换机 routingKey 和 bindingKey 的匹配规则
    private boolean routeTopic(Binding binding, Message message) {
        // 先将这两个 key 用 . 分割
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");
        // 使用两个双指针，遍历两个的数组
        int bindingIndex = 0;
        int routingIndex = 0;
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {

            if (bindingTokens[bindingIndex].equals("*")) {
                // 【情况一】* 可以匹配任意一个部分
                bindingIndex++;
                routingIndex++;
                continue;
            }

            if (bindingTokens[bindingIndex].equals("#")) {
                bindingIndex++;
                if (bindingIndex == bindingTokens.length) {
                    // 【情况二】 # 后面没有内容了，# 可以匹配多个任意部分，匹配成功了
                    return true;
                }
                // 【情况三】# 后面还有内容，拿着这个内容，去 routingTokens 中在当前位置往后查找，找到一样的，返回位置
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                if (routingIndex == -1) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
                continue;
            }

            if (bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                // 【情况四】普通字符串，两个内容要完全一致
                bindingIndex++;
                routingIndex++;
                continue;
            }
            return false;
        }
        // 双方都到达末尾，匹配成功
        return bindingIndex == bindingTokens.length && routingIndex == routingTokens.length;
    }

    // 在 routingTokens 中，从下标  routingIndex 位置，开始寻找 bindingToken
    // 找到返回 对应的下标，找不到返回 -1
    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if (routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }
}
